#pragma once

#include <iostream>
#include <queue>
#include <pthread.h>

const int max_cap = 5;

template <class T>
class blockQueue
{
public:
    blockQueue(int cap = max_cap)
        : _cap(cap), _mutex(new pthread_mutex_t), _consumer_cond(new pthread_cond_t), _productor_cond(new pthread_cond_t)
    {
        pthread_mutex_init(_mutex, nullptr);
        pthread_cond_init(_consumer_cond, nullptr);
        pthread_cond_init(_productor_cond, nullptr);
    }
    ~blockQueue()
    {
        pthread_mutex_destroy(_mutex);
        pthread_cond_destroy(_consumer_cond);
        pthread_cond_destroy(_productor_cond);
        delete _mutex;
        delete _consumer_cond;
        delete _productor_cond;
    }

    bool isFull() { return _q.size() == _cap; }
    bool isEmpty() { return _q.empty(); }

    // productor call
    void push(const T &input)
    {
        pthread_mutex_lock(_mutex);

        while (isFull()) // 阻塞队列为满, 不push, 等待
        {
            pthread_cond_wait(_productor_cond, _mutex);
        }

        _q.push(input);
        // 此时阻塞队列中至少有一个数据，唤醒消费者
        pthread_cond_signal(_consumer_cond);

        pthread_mutex_unlock(_mutex);
    }

    // consumer call
    void pop(T *output)
    {
        pthread_mutex_lock(_mutex);

        // if (isEmpty()) // 阻塞队列为空, 不pop, 等待
        // 有多个消费者时，若一个消费者在被唤醒后申请锁时，
        // 锁正被其它消费者持有，并且阻塞队列被其它消费者清空，那么if会出错
        while (isEmpty())
        {
            pthread_cond_wait(_consumer_cond, _mutex);
        }

        *output = _q.front();
        _q.pop();
        // 此时阻塞队列中至少有一个空位, 唤醒生产者
        pthread_cond_signal(_productor_cond);

        pthread_mutex_unlock(_mutex);
    }

private:
    std::queue<T> _q;
    int _cap;
    pthread_mutex_t *_mutex;
    pthread_cond_t *_consumer_cond;
    pthread_cond_t *_productor_cond;
};